﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Overby
{
    [Serializable]
    public class SortQueue
    {
        private Queue<SortExpression> queue;
        private int capacity;

        /// <summary>
        /// Whether the SortQueue will handle the toggling of sort directions. Set to false if calling code will always explicitly specify the sort direction. Otherwise, set to true.
        /// </summary>
        public bool ManageDirection { get; set; }

        /// <summary>
        /// Field names that will always have unique values, such as a primary key field. Specifying unique field names will improve performance as secondary sorting will not be needed.
        /// </summary>
        public List<string> UniqueFields { get; set; }

        /// <param name="capacity">The maximum size of the sort queue. This should be less than or equal to the # of fields that can be sorted on.</param>
        /// <param name="manageDirection">Whether the SortQueue will handle the toggling of sort directions. Set to false if calling code will always explicitly specify the sort direction. Otherwise, set to true.</param>
        /// <param name="uniqueFields">Field names that will always have unique values, such as a primary key field. Specifying unique field names will improve performance as secondary sorting will not be needed.</param>
        public SortQueue(int capacity, bool manageDirection, params string[] uniqueFields)
        {
            ManageDirection = manageDirection;
            UniqueFields = new List<string>(uniqueFields);
            queue = new Queue<SortExpression>(capacity);
            this.capacity = capacity;
        }

        /// <summary>
        /// Adds a sort expression to the sort queue.
        /// </summary>
        /// <param name="field">A parsable sort expression. Example: LastName ASC</param>
        public void Add(string sortExpression)
        {
            Add(SortExpression.Parse(sortExpression));
        }

        /// <summary>
        /// Adds a sort expression to the sort queue.
        /// </summary>
        /// <param name="field">The name of the field to sort.</param>
        /// <param name="descending">Whether to sort in descending order or not.</param>
        public void Add(string field, bool descending)
        {
            Add(new SortExpression { Field = field, Descending = descending });
        }

        /// <summary>
        /// Adds a sort expression to the sort queue.
        /// </summary>
        public void Add(SortExpression expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression", "expression is null.");

            // if the field is present in the queue
            if (queue.Select(x => x.Field).Contains(expression.Field))
            {
                // if the SortQueue is managing sort direction and the field was at the top of the queue,
                // then toggle the sort direction for that field
                if (ManageDirection && queue.Last().Field == expression.Field)
                {
                    expression = queue.Last();
                    expression.Descending = !expression.Descending;
                }

                // remove the sort expression from the queue
                queue = new Queue<SortExpression>(queue.Where(x => x.Field != expression.Field));
            }

            // if the field is not present in the queue
            // remove oldest sort expressions, making room for the new sort expression
            else while (queue.Count >= capacity)
                    queue.Dequeue();

            
            queue.Enqueue(expression);
        }

        /// <summary>
        /// Sorts the items according to the expressions in the sort queue.
        /// </summary>
        /// <param name="items">The items to be sorted.</param>        
        public IOrderedQueryable<T> Sort<T>(IQueryable<T> items)
        {
            if (items == null)
                throw new ArgumentNullException("items", "items is null.");

            if (queue.Count == 0)
                return items.OrderBy(x => 0);

            // convert the queue to a stack so that fields
            // are sorted in the reverse order that they were added
            Stack<SortExpression> stack = new Stack<SortExpression>(queue);

            // primary sort
            SortExpression sortExpression = stack.Pop();
            IOrderedQueryable<T> sortedItems = sortExpression.Descending
                            ? items.OrderByDescending(sortExpression.Field)
                            : items.OrderBy(sortExpression.Field);

            // secondary+ sorts only if the primary sort was performed on a non-unique field
            while (!UniqueFields.Contains(sortExpression.Field) && stack.Count > 0)
            {
                sortExpression = stack.Pop();
                sortedItems = sortExpression.Descending
                    ? sortedItems.ThenByDescending(sortExpression.Field)
                    : sortedItems.ThenBy(sortExpression.Field);
            }

            return sortedItems;
        }
    }
}